package com.ym.core.shiro;

import cn.hutool.extra.spring.SpringUtil;
import com.ym.common.constant.UserDetails;
import com.ym.common.enums.UserUserTypeEnum;
import com.ym.core.redis.RedisKeyEnum;
import com.ym.core.redis.RedisKeyUtil;
import com.ym.core.redis.RedisUtil;
import org.apache.commons.lang3.StringUtils;
import org.apache.shiro.session.Session;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.concurrent.TimeUnit;

/**
 * 执行登录成功后，保存登录人和token关系的操作
 *
 * @author: 李涛
 * @version: 2019年08月28日 8:52
 */
public class ShiroTokenManager {


	private static final Logger LOGGER = LoggerFactory.getLogger(ShiroTokenManager.class);


	private static final RedisUtil redisUtil = SpringUtil.getBean(RedisUtil.class);

	/**
	 * 用户登录过期时间
	 */
	public static final long TOKEN_EXPIRATION_TIME = TimeUnit.MINUTES.toMillis(SpringUtil.getBean(ShiroSessionConfig.class).getExpireTime());

	/**
	 * 最大同时多端登陆数量
	 */
	public static final long MAX_LOGIN_NUM = SpringUtil.getBean(ShiroSessionConfig.class).getMaxSession();

	/**
	 * 默认的shiroKEY前缀，如有修改请同步
	 */
	public static final String DEFAULT_SESSION_KEY_PREFIX = RedisKeyEnum.SHIRO_SESSION.build(":");

	/**
	 * 用户token绑定关系
	 */
	public static final String DEFAULT_SESSION_KEY_PREFIX_USER = RedisKeyUtil.keyBuilder(RedisKeyEnum.SHIRO_TOKEN_USER);


	/**
	 * patient和token绑定关系
	 */
	public static final String DEFAULT_SESSION_KEY_PREFIX_PATIENT = RedisKeyUtil.keyBuilder(RedisKeyEnum.SHIRO_TOKEN_PATIENT);


	/**
	 * DOCTOR和token绑定关系
	 */
	public static final String DEFAULT_SESSION_KEY_PREFIX_DOCTOR = RedisKeyUtil.keyBuilder(RedisKeyEnum.SHIRO_TOKEN_DOCTOR);

	/**
	 * 绑定用户和token的关系
	 */
	public static void bindUserToken() {
		UserDetails loginUser = ShiroUtil.getLoginUser(true);
		if (loginUser == null) {
			return;
		}
		RedisKeyEnum redisKeyEnum = null;
		String DEFAULT_SESSION_KEY_LOCCAL = null;
		Long userId = loginUser.getId();
		if (loginUser.getUserTypeEnum() == UserUserTypeEnum.SYSTEM) {
			redisKeyEnum = RedisKeyEnum.SHIRO_TOKEN_USER;
			DEFAULT_SESSION_KEY_LOCCAL = DEFAULT_SESSION_KEY_PREFIX_USER;
		} else if (loginUser.getUserTypeEnum() == UserUserTypeEnum.DOCTOR) {
			redisKeyEnum = RedisKeyEnum.SHIRO_TOKEN_DOCTOR;
			DEFAULT_SESSION_KEY_LOCCAL = DEFAULT_SESSION_KEY_PREFIX_DOCTOR;
		} else if (loginUser.getUserTypeEnum() == UserUserTypeEnum.PATIENT) {
			redisKeyEnum = RedisKeyEnum.SHIRO_TOKEN_PATIENT;
			DEFAULT_SESSION_KEY_LOCCAL = DEFAULT_SESSION_KEY_PREFIX_PATIENT;
		}
		String token = ShiroUtil.getSessionId();
		//获取该用户当前登录多个token关系
		String key = RedisKeyUtil.keyBuilder(redisKeyEnum, String.valueOf(userId));
		List<String> tokens = redisUtil.lGet(key, 0, -1);
		// 如果已经绑定退出
		if(tokens.contains(token)){
			return;
		}
		Iterator<String> iterator = tokens.iterator();
		while (iterator.hasNext()) {
			String redisToken = iterator.next();
			boolean hasKey = redisUtil.hasKey(DEFAULT_SESSION_KEY_PREFIX + redisToken);
			if (!hasKey) {
				iterator.remove();
				redisUtil.lRemove(key, 0, redisToken);
				LOGGER.info("清楚用户无效token{}！", redisToken);
			}
		}
		if (MAX_LOGIN_NUM != -1) {
			//如果登陆用户超过最大登陆数量，提出前边的用户
			long diffLoginNum = tokens.size() - MAX_LOGIN_NUM + 1;
			for (long i = 0; i < diffLoginNum; i++) {
				String expireToken = redisUtil.lPop(key);
				LOGGER.info("超过最大同时登陆用户数量{},剔除token:{}！", MAX_LOGIN_NUM, expireToken);
				redisUtil.del(DEFAULT_SESSION_KEY_PREFIX + expireToken);
			}
		}
		// 添加当前token
		redisUtil.lSet(key, token);
		// 刷新用户组token
		redisUtil.expire(key, TOKEN_EXPIRATION_TIME / 1000);
		// 在session中放入当前用户的ID
		ShiroUtil.getSession().setAttribute(DEFAULT_SESSION_KEY_LOCCAL, userId);
		LOGGER.info("绑定用户{}的token成功{}！", userId, token);
	}

	/**
	 * 移除指定用户的所有token
	 *
	 * @param userId
	 */
	public static void kickOutUser(Long userId, UserUserTypeEnum loginTypeEnum) {
		if (userId == null) {
			return;
		}
		RedisKeyEnum redisKeyEnum = null;
		if (loginTypeEnum == UserUserTypeEnum.PATIENT) {
			redisKeyEnum = RedisKeyEnum.SHIRO_TOKEN_PATIENT;
		} else if (loginTypeEnum == UserUserTypeEnum.DOCTOR) {
			redisKeyEnum = RedisKeyEnum.SHIRO_TOKEN_DOCTOR;
		} else if (loginTypeEnum == UserUserTypeEnum.SYSTEM) {
			redisKeyEnum = RedisKeyEnum.SHIRO_TOKEN_USER;
		}
		//获取该用户当前登录多个token关系
		String key = RedisKeyUtil.keyBuilder(redisKeyEnum, String.valueOf(userId));
		List<String> tokens = redisUtil.lGet(key, 0, -1);
		for (String token : tokens) {
			redisUtil.del(DEFAULT_SESSION_KEY_PREFIX + token);
		}
		redisUtil.del(key);
		LOGGER.info("清楚用户{}的token成功！", userId);
	}

	/**
	 * 移除指定用户的指定token
	 *
	 * @param userId       用户ID
	 * @param token        指定token
	 * @param clearSession 是否要清楚session
	 */
	public static void kickOutUserByToken(Long userId, String token, UserUserTypeEnum loginTypeEnum, boolean clearSession) {
		if (StringUtils.isBlank(token)) {
			return;
		}
		if (userId == null) {
			return;
		}
		RedisKeyEnum redisKeyEnum = null;
		if (loginTypeEnum == UserUserTypeEnum.PATIENT) {
			redisKeyEnum = RedisKeyEnum.SHIRO_TOKEN_PATIENT;
		} else if (loginTypeEnum == UserUserTypeEnum.DOCTOR) {
			redisKeyEnum = RedisKeyEnum.SHIRO_TOKEN_DOCTOR;
		} else if (loginTypeEnum == UserUserTypeEnum.SYSTEM) {
			redisKeyEnum = RedisKeyEnum.SHIRO_TOKEN_USER;
		}
		//获取该用户当前登录多个token关系
		String key = RedisKeyUtil.keyBuilder(redisKeyEnum, String.valueOf(userId));
		List<String> tokens = redisUtil.lGet(key, 0, -1);
		long expireTime = redisUtil.getExpire(key);
		Iterator<String> iterator = tokens.iterator();
		while (iterator.hasNext()) {
			String redisKey = iterator.next();
			// 判定这个key是否存在，不存在也移除
			String removeKey = DEFAULT_SESSION_KEY_PREFIX + redisKey;
			boolean hasKey = redisUtil.hasKey(removeKey);
			if (!hasKey || redisKey.equals(token)) {
				iterator.remove();
			}
		}
		if (tokens.isEmpty()) {
			redisUtil.del(key);
		} else {
			redisUtil.del(key);
			redisUtil.sSetAndTime(key, expireTime, tokens.toArray(new String[tokens.size()]));
		}
		//如果要清楚登录状态，则清除
		if (clearSession) {
			String removeKey = DEFAULT_SESSION_KEY_PREFIX + token;
			boolean hasKey = redisUtil.hasKey(removeKey);
			if (hasKey) {
				redisUtil.del(removeKey);
			}
		}
		LOGGER.info("清楚用户{}的token成功{}！", userId, token);
	}

	/**
	 * 指定用户操作的时候，刷新token持续时间
	 */
	public static void refreshToken(Session session) {
		UserDetails loginUser = ShiroUtil.getLoginUser(true);
		if (loginUser == null) {
			return;
		}
		RedisKeyEnum redisKeyEnum = null;
		String DEFAULT_SESSION_KEY_LOCCAL = null;
		if (loginUser.getUserTypeEnum() == UserUserTypeEnum.PATIENT) {
			redisKeyEnum = RedisKeyEnum.SHIRO_TOKEN_PATIENT;
			DEFAULT_SESSION_KEY_LOCCAL = DEFAULT_SESSION_KEY_PREFIX_PATIENT;
		} else if (loginUser.getUserTypeEnum() == UserUserTypeEnum.DOCTOR) {
			redisKeyEnum = RedisKeyEnum.SHIRO_TOKEN_DOCTOR;
			DEFAULT_SESSION_KEY_LOCCAL = DEFAULT_SESSION_KEY_PREFIX_DOCTOR;
		} else if (loginUser.getUserTypeEnum() == UserUserTypeEnum.SYSTEM) {
			redisKeyEnum = RedisKeyEnum.SHIRO_TOKEN_USER;
			DEFAULT_SESSION_KEY_LOCCAL = DEFAULT_SESSION_KEY_PREFIX_USER;
		}
		Object attribute = session.getAttribute(DEFAULT_SESSION_KEY_LOCCAL);
		if (attribute == null) {
			return;
		}
		//获取该用户当前登录token关系,刷新时间
		String key = RedisKeyUtil.keyBuilder(redisKeyEnum, String.valueOf(attribute));
		redisUtil.expire(key, TOKEN_EXPIRATION_TIME / 1000);
	}

	/**
	 * session自动失效，清理当前session的token
	 *
	 * @param session
	 */
	public static void deleteTokenAfterSessionDelete(Session session) {
		Object attribute = session.getAttribute(DEFAULT_SESSION_KEY_PREFIX_USER);
		if (attribute != null) {
			kickOutUserByToken((Long) attribute, session.getId().toString(), UserUserTypeEnum.SYSTEM, false);
			return;
		}
		attribute = session.getAttribute(DEFAULT_SESSION_KEY_PREFIX_PATIENT);
		if (attribute != null) {
			kickOutUserByToken((Long) attribute, session.getId().toString(), UserUserTypeEnum.PATIENT, false);
			return;
		}
		attribute = session.getAttribute(DEFAULT_SESSION_KEY_PREFIX_DOCTOR);
		if (attribute != null) {
			kickOutUserByToken((Long) attribute, session.getId().toString(), UserUserTypeEnum.DOCTOR, false);
			return;
		}
	}


	/**
	 * 获取活跃用户数量
	 */
	public static long getActiveUserNum() {
		UserDetails loginUser = ShiroUtil.getLoginUser(true);
		if (loginUser == null) {
			return 0;
		}
		String DEFAULT_SESSION_KEY_LOCCAL = null;
		if (loginUser.getUserTypeEnum() == UserUserTypeEnum.SYSTEM) {
			DEFAULT_SESSION_KEY_LOCCAL = DEFAULT_SESSION_KEY_PREFIX_USER;
		} else if (loginUser.getUserTypeEnum() == UserUserTypeEnum.PATIENT) {
			DEFAULT_SESSION_KEY_LOCCAL = DEFAULT_SESSION_KEY_PREFIX_PATIENT;
		} else if (loginUser.getUserTypeEnum() == UserUserTypeEnum.DOCTOR) {
			DEFAULT_SESSION_KEY_LOCCAL = DEFAULT_SESSION_KEY_PREFIX_DOCTOR;
		}
		Set<String> keys = redisUtil.keys(DEFAULT_SESSION_KEY_LOCCAL + "*");
		LOGGER.info("活跃用户数量{}！", keys.size());
		return keys.size();
	}
}
